package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.TermsQueryField;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private CategoryFeignClient categoryFeignClient;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private AlbumIndexRepository albumIndexRepository;

    @Autowired
    private ThreadPoolExecutor ThreadPoolExecutor;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    /*
     * 上架专辑
     * */
    @Override
    public void upperAlbum(Long albumId) {
        // 创建一个AlbumInfoIndex对象，用于封装专辑信息
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        // 异步获取专辑信息
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            // 调用Feign客户端获取专辑信息
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            // 断言专辑信息不为空
            Assert.notNull(albumInfoResult, "专辑为空");
            AlbumInfo albumInfo = albumInfoResult.getData();
            // 将专辑信息复制到索引对象
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);
            return albumInfo;
        }, ThreadPoolExecutor);

        // 异步获取专辑属性信息
        CompletableFuture<Void> albumAttributeValueCompletableFuture = CompletableFuture.runAsync(() -> {
            // 调用Feign客户端获取专辑属性信息
            Result<List<AlbumAttributeValue>> albumAttributeValueResult = albumInfoFeignClient.findAlbumAttributeValue(albumId);
            // 断言专辑属性信息不为空
            Assert.notNull(albumAttributeValueResult, "专辑属性为空");
            List<AlbumAttributeValue> albumAttributeValueList = albumAttributeValueResult.getData();
            // 如果专辑属性列表不为空，处理并设置到索引对象
            if (!CollectionUtils.isEmpty(albumAttributeValueList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueList.stream().map(albumAttributeValue -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    // 封装专辑属性的ID和值ID
                    attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                    attributeValueIndex.setValueId(albumAttributeValue.getValueId());

                    return attributeValueIndex;
                }).collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
        }, ThreadPoolExecutor);

        // 在获取专辑信息后，异步获取三级分类数据
        CompletableFuture<Void> categoryViewCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            // 调用Feign客户端获取分类视图信息
            Result<BaseCategoryView> categoryViewResult = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
            // 断言分类信息不为空
            Assert.notNull(categoryViewResult, "分类信息为空");
            BaseCategoryView baseCategoryView = categoryViewResult.getData();
            // 设置分类ID到索引对象
            albumInfoIndex.setCategory1Id(baseCategoryView.getId());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, ThreadPoolExecutor);

        // 在获取专辑信息后，异步根据用户ID获取用户信息
        CompletableFuture<Void> userInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            // 调用Feign客户端获取用户信息
            Result<UserInfoVo> userInfoResult = userInfoFeignClient.getUserInfo(albumInfo.getUserId());
            // 断言用户信息不为空
            Assert.notNull(userInfoResult, "用户信息为空");
            UserInfoVo userInfoVo = userInfoResult.getData();
            // 设置主播名称到索引对象
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());

        }, ThreadPoolExecutor);

        // 等待所有异步任务完成
        CompletableFuture.allOf(albumAttributeValueCompletableFuture, categoryViewCompletableFuture, userInfoCompletableFuture, albumInfoCompletableFuture).join();


        Integer playStatNum = new Random().nextInt(100000);
        Integer subscribeStatNum = new Random().nextInt(1000000);
        Integer buyStatNum = new Random().nextInt(100000);
        Integer commentStatNum = new Random().nextInt(1000000);
        Double hotScore = new Random().nextDouble(10000000);
        albumInfoIndex.setPlayStatNum(playStatNum);
        albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
        albumInfoIndex.setBuyStatNum(buyStatNum);
        albumInfoIndex.setCommentStatNum(commentStatNum);
        albumInfoIndex.setHotScore(hotScore);

        // 保存专辑索引信息
        albumIndexRepository.save(albumInfoIndex);

        //  给提词类赋值
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));
        suggestIndexRepository.save(suggestIndex);


        //赋值
        SuggestIndex suggestIndexInfo = new SuggestIndex();
        suggestIndexInfo.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        suggestIndexInfo.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndexInfo.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumIntro()}));
        suggestIndexInfo.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumIntro())}));
        suggestIndexInfo.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumIntro())}));
        suggestIndexRepository.save(suggestIndexInfo);
    }


    /*
     * 下架专辑
     * */
    @Override
    public void lowerAlbum(Long albumId) {
        albumIndexRepository.deleteById(albumId);
    }


    /**
     * 专辑搜索列表
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        // 构建Elasticsearch查询请求
        SearchRequest searchRequest = this.buildQueryDsl(albumIndexQuery);

        // 执行搜索操作
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        } catch (IOException e) {
            // 捕获并处理IO异常
            throw new RuntimeException(e);
        }

        // 创建搜索响应对象并解析搜索结果
        AlbumSearchResponseVo albumSearchResponseVo = this.parseSearchResult(searchResponse);
        // 设置总记录数
        albumSearchResponseVo.setTotal(searchResponse.hits().total().value());
        // 设置每页记录数
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        // 设置当前页码
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());

        // 计算总页数
        Long totalPages = (albumSearchResponseVo.getTotal() + albumSearchResponseVo.getPageSize() - 1) / albumSearchResponseVo.getPageSize();
        albumSearchResponseVo.setTotalPages(totalPages);

        // 返回搜索结果响应对象
        return albumSearchResponseVo;
    }
    private AlbumSearchResponseVo parseSearchResult(SearchResponse<AlbumInfoIndex> searchResponse) {
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        List<AlbumInfoIndexVo> albumInfoIndexVoList = searchResponse.hits().hits().stream().map(hit ->
        {
            //  获取里面的数据
            AlbumInfoIndex albumInfoIndex = hit.source();
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);

            //  判断用户是否根据关键词检索，如果根据关键词检索则标题必须是高亮字段！
            if (null != hit.highlight().get("albumTitle")) {
                albumInfoIndexVo.setAlbumTitle(hit.highlight().get("albumTitle").get(0));
            }
            return albumInfoIndexVo;
        }).collect(Collectors.toList());

        albumSearchResponseVo.setList(albumInfoIndexVoList);
        return albumSearchResponseVo;
    }
    /**
     * 构建ES查询请求
     *
     * @param albumIndexQuery 专辑索引查询对象，包含查询参数如关键字、分类ID、排序等
     * @return SearchRequest 构建的ES查询请求对象
     */
    private SearchRequest buildQueryDsl(AlbumIndexQuery albumIndexQuery) {
        // 初始化搜索请求构建器，并指定索引名为"albuminfo"
        SearchRequest.Builder searchRequest = new SearchRequest.Builder();
        searchRequest.index("albuminfo");

        // 初始化查询构建器
        Query.Builder querybuilder = new Query.Builder();
        BoolQuery.Builder boolbuilder = new BoolQuery.Builder();

        // 设置分页参数
        searchRequest.size(albumIndexQuery.getPageSize());
        searchRequest.from((albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize());

        // 将构建好的bool查询添加到查询请求中
        querybuilder.bool(boolbuilder.build());
        searchRequest.query(querybuilder.build());

        // 如果关键字不为空，设置匹配标题和简介的查询条件
        if (!StringUtils.isEmpty(albumIndexQuery.getKeyword())) {
            boolbuilder.should(s -> s.match(m -> m.field("albumTitle").query(albumIndexQuery.getKeyword())))
                    .should(s -> s.match(m -> m.field("albumIntro").query(albumIndexQuery.getKeyword())));
        }

        // 根据一级、二级、三级分类ID添加过滤条件
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory1Id())) {
            boolbuilder.filter(f -> f.term(t -> t.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory2Id())) {
            boolbuilder.filter(f -> f.term(t -> t.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory3Id())) {
            boolbuilder.filter(f -> f.term(t -> t.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }

        // 如果排序条件有效，根据排序类型添加排序字段
        //  排序规则 综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]；asc:升序 desc:降序
        if (!StringUtils.isEmpty(albumIndexQuery.getOrder())) {
            String[] split = albumIndexQuery.getOrder().split(":");
            if (split != null && split.length == 2) {
                //  声明一个排序字段
                String field = "";
                switch (split[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "playStatNum";
                        break;
                    case "3":
                        field = "createTime";
                        break;
                }
                String finalField = field;
                searchRequest.sort(s -> s.field(f -> f.field(finalField).order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
            }
        }

        // 高亮标题中的匹配关键字
        searchRequest.highlight(h -> h.fields("albumTitle", hf -> hf.preTags("<span style=color:#f86442>").postTags("</span>")));

        // 根据属性列表添加嵌套过滤条件
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            //  attribute: 属性id:属性值id
            for (String attribute : attributeList) {
                String[] spilt = attribute.split(":");
                if (null != spilt && spilt.length == 2) {
                    boolbuilder.filter(f -> f.nested(n -> n.path("attributeValueIndexList")
                            .query(q -> q.bool(b -> b.filter(fl -> fl.term(t -> t.field("attributeValueIndexList.attributeId").value(spilt[0])))
                                    .filter(fl -> fl.term(t -> t.field("attributeValueIndexList.valueId").value(spilt[1])))))));
                }
            }
        }

        // 构建并返回搜索请求
        SearchRequest build = searchRequest.build();
        log.info("查询条件为：{}", build);
        System.out.println("build====" + build);
        return build;
    }

    /*
     * 关键字自动补全功能
     * 该方法旨在通过分析 Elasticsearch 索引中的数据，为给定的关键字提供自动补全建议
     * 补全建议优先从专门的 suggestinfo 索引中获取，如果不足，再从更广泛的数据中获取
     *
     * @param keyword 用户输入的关键字，用于生成补全建议
     * @return 返回一个字符串列表，包含最多10个补全建议
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        // 构建搜索请求，用于从suggestinfo索引中获取补全建议
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        searchRequestBuilder.index("suggestinfo").suggest(s -> s.suggesters("suggestionKeyword", f -> f.prefix(keyword).completion(c -> c.field("keyword").skipDuplicates(true).size(10).fuzzy(f1 -> f1.fuzziness("auto"))))
                .suggesters("suggestionKeywordPinyin", f -> f.prefix(keyword).completion(c -> c.field("keywordPinyin").skipDuplicates(true).size(10).fuzzy(f2 -> f2.fuzziness("auto"))))
                .suggesters("suggestionKeywordSequence", f -> f.prefix(keyword).completion(c -> c.field("keywordSequence").skipDuplicates(true).size(10).fuzzy(f3 -> f3.fuzziness("auto")))));

        SearchResponse<SuggestIndex> searchResponse = null;
        // 执行搜索请求，并处理潜在的IO异常
        try {
            searchResponse = elasticsearchClient.search(searchRequestBuilder.build(), SuggestIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 使用HashSet集合存储补全结果，避免重复
        HashSet<String> titleSet = new HashSet<>();
        // 解析搜索响应中的补全建议，分别针对不同的suggesters
        titleSet.addAll(this.parseResultData(searchResponse, "suggestionKeyword"));
        titleSet.addAll(this.parseResultData(searchResponse, "suggestionKeywordPinyin"));
        titleSet.addAll(this.parseResultData(searchResponse, "suggestionKeywordSequence"));

        // 如果补全结果不足10个，尝试从更广泛的数据中获取更多建议
        if (titleSet.size() < 10) {
            SearchResponse<SuggestIndex> response = null;
            try {
                // 执行更广泛的匹配搜索，针对suggestinfo索引中的title字段
                response = elasticsearchClient.search(s -> s.index("suggestinfo").query(q -> q.match(m -> m.field("title").query(keyword))), SuggestIndex.class);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            // 遍历搜索结果，添加新的补全建议，直到达到10个为止
            for (Hit<SuggestIndex> hit : response.hits().hits()) {
                SuggestIndex suggestIndex = hit.source();
                titleSet.add(suggestIndex.getTitle());
                if (titleSet.size() == 10) {
                    break;
                }
            }
        }
        // 返回补全建议列表
        return new ArrayList<>(titleSet);
    }
    /**
     * 解析搜索建议响应数据
     * <p>
     * 该方法用于从搜索响应中提取建议数据，根据提供的搜索名称过滤并返回一个包含建议标题的集合
     *
     * @param searchResponse 搜索响应对象，包含搜索建议数据
     * @param suggestName    搜索建议的名称，用于从响应中选择特定的建议集合
     * @return 返回一个字符串集合，每个字符串是一个搜索建议的标题
     */
    private Collection<String> parseResultData(SearchResponse<SuggestIndex> searchResponse, String suggestName) {
        // 创建一个列表来存储解析出来的搜索建议标题
        List<String> suggestList = new ArrayList<>();

        // 获取按建议名称分组的映射，键为建议名称，值为建议列表
        Map<String, List<Suggestion<SuggestIndex>>> groupBySuggestionListAggMap = searchResponse.suggest();

        // 遍历特定建议名称对应的建议列表
        groupBySuggestionListAggMap.get(suggestName).forEach(item -> {
            // 获取每个建议的完成建议对象
            CompletionSuggest<SuggestIndex> completionSuggest = item.completion();
            // 遍历完成建议中的每个选项
            completionSuggest.options().forEach(it -> {
                // 获取建议源对象
                SuggestIndex suggestIndex = it.source();
                // 提取并添加建议标题到列表中
                suggestList.add(suggestIndex.getTitle());
            });
        });

        // 返回解析出的建议标题列表
        return suggestList;
    }

    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        Result<List<BaseCategory3>> topBaseCategory3ListResult = categoryFeignClient.findTopBaseCategory3(category1Id);
        List<BaseCategory3> category3List = topBaseCategory3ListResult.getData();

        List<Long> category3IdList = category3List.stream().map(BaseCategory3::getId).collect(Collectors.toList());
        Map<Long, BaseCategory3> category3IdToMap = category3List.stream().collect(Collectors.toMap(BaseCategory3::getId, BaseCategory3 -> BaseCategory3));

        List<FieldValue> valueList = category3IdList.stream().map(id -> FieldValue.of(id)).collect(Collectors.toList());
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        searchRequestBuilder.index("albuminfo").query(q ->q.terms(f ->f.field("category3Id").terms(new TermsQueryField.Builder().value(valueList).build())));

        searchRequestBuilder.aggregations("groupByCategory3IdAgg",a ->a.terms(t ->t.field("category3Id"))
                .aggregations("topTenHotScoreAgg",a1 ->a1.topHits(s ->s.size(6).sort(sort ->sort.field(f ->f.field("hotScore").order(SortOrder.Desc))))));

        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse=elasticsearchClient.search(searchRequestBuilder.build(), AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List<Map<String,Object>> result = new ArrayList<>();
        Aggregate groupByCategory3IdAgg = searchResponse.aggregations().get("groupByCategory3IdAgg");
        groupByCategory3IdAgg.lterms().buckets().array().forEach(item ->{
            List<AlbumInfoIndex> albumInfoIndexList = new ArrayList<>();
            long category3Id = item.key();
            Aggregate topTenHotScoreAgg = item.aggregations().get("topTenHotScoreAgg");
            topTenHotScoreAgg.topHits().hits().hits().forEach(hit ->{
                String json = hit.source().toString();
                AlbumInfoIndex albumInfoIndex = JSON.parseObject(json, AlbumInfoIndex.class);
                albumInfoIndexList.add(albumInfoIndex);
            });
            Map<String, Object> map = new HashMap<>();
            map.put("baseCategory3",category3IdToMap.get(category3Id));
            map.put("list",albumInfoIndexList);
            result.add(map);
        });

        return result;
    }
}
